﻿/******************************************************************************************************
Copyright (c) 2014 Steven Giacomelli (stevegiacomelli@gmail.com)

Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies
or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
******************************************************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;

namespace SharpGeo
{
    /// <summary>
    /// A class to handle geometry attributes
    /// </summary>
    sealed public class GeometryAttributes : IEnumerable<KeyValuePair<string, string>>
    {
        //*******************************************************************//
        //*                     Design Logic                                *//
        //*                                                                 *//
        //* -   In order to avoid having Geometry objects directly expose   *//
        //*     a dictionary object, this object wraps a generic dictionary,*//
        //*     and exposes an IReadOnlyDictionary interface as well as a   *//
        //*     readonly indexing method and two simple add/clear methods   *//
        //*                                                                 *//
        //* -   It may be worthwhile to not directly store attributes on    *//
        //*     objects but rather in a common 'database' linked to         *//
        //*     the objects UniqueID -> but for simplicity, right now the   *//
        //*     this will be used for attribute                             *//
        //*                                                                 *//
        //*-    As well, this class uses the built in Lazy initialization   *//
        //*     in a thread unsafe context to avoid the additional          *//
        //*     scaffolding -- in the current design it is <<unlikely>>     *//
        //*     that this will be used by multiple threads at once,         *//
        //*     however a simple lock isemployed for basic thread safety    *//
        //*******************************************************************//

        #region Constructor

        /// <summary>
        /// Constructs an empty GeometryAttributesDictionary
        /// </summary>
        public GeometryAttributes()
        {
            lock (this)
            {
                _attributes = new Lazy<Dictionary<string, string>>(false);
            }
        }

        private GeometryAttributes(Lazy<Dictionary<string, string>> attributes, bool asReadonly)
        {
            lock (this)
            {
                _attributes = attributes;
                _asReadOnly = asReadonly;
            }
        }

        #endregion Constructor

        #region Fields

        private Lazy<Dictionary<string, string>> _attributes;
        private bool _asReadOnly = false;

        #endregion Fields

        #region Properties

        /// <summary>
        /// Gets the selected attribute value
        /// </summary>
        /// <param name="attribute">The attribute to retrieve the value of</param>
        /// <returns>The attribute value</returns>
        public string this[string attribute]
        {
            get
            {
                lock (this)
                {
                    return _attributes.IsValueCreated ? _attributes.Value[attribute] : string.Empty;
                }
            }
        }

        /// <summary>
        /// Gets the number of attributes
        /// </summary>
        public int Count
        {
            get
            {
                lock (this)
                {
                    return _attributes.IsValueCreated ? _attributes.Value.Count : 0;
                }
            }
        }

        /// <summary>
        /// Returns a ReadOnly version
        /// </summary>
        public GeometryAttributes AsReadOnly { get { return new GeometryAttributes(_attributes, true); } }

        /// <summary>
        /// Returns true if the key is contained in this GeometryAttributes
        /// </summary>
        /// <param name="key">The key to check</param>
        /// <returns>True if this contains the specified key</returns>
        public bool ContainsKey(string key)
        {
            lock (this)
            {
                return _attributes.IsValueCreated ? _attributes.Value.ContainsKey(key) : false;
            }
        }

        /// <summary>
        /// Gets an enumerator that enumerates the keys
        /// </summary>
        public IEnumerable<string> Keys
        {
            get
            {
                lock (this)
                {
                    return _attributes.IsValueCreated ? _attributes.Value.Keys : Enumerable.Empty<string>();
                }
            }
        }

        /// <summary>
        /// Gets an enumerator that enumerates the values
        /// </summary>
        public IEnumerable<string> Values
        {
            get
            {
                lock (this)
                {
                    return _attributes.IsValueCreated ? _attributes.Value.Values : Enumerable.Empty<string>();
                }
            }
        }

        IEnumerator<KeyValuePair<string, string>> IEnumerable<KeyValuePair<string, string>>.GetEnumerator()
        {
            lock (this)
            {
                if (_attributes.IsValueCreated)
                    return _attributes.Value.GetEnumerator();

                return (IEnumerator<KeyValuePair<string, string>>)
                    Enumerable.Empty<KeyValuePair<string, string>>();
            }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            lock (this)
            {
                if (_attributes.IsValueCreated)
                    return _attributes.Value.GetEnumerator();

                return (IEnumerator<KeyValuePair<string, string>>)
                    Enumerable.Empty<KeyValuePair<string, string>>();
            }
        }

        #endregion Properties

        #region Methods

        /// <summary>
        /// Adds an attribute with a value
        /// </summary>
        /// <param name="attribute">The name of the attribute</param>
        /// <param name="value">The value of the attribute</param>
        public void AddAttribute(string attribute, string value)
        {
            if (_asReadOnly)
                return;

            lock (this)
            {
                if (!_attributes.Value.ContainsKey(attribute))
                {
                    _attributes.Value.Add(attribute, value);
                }
            }
        }

        /// <summary>
        /// Clears all attributes
        /// </summary>
        public void Clear()
        {
            if (_asReadOnly)
                return;

            lock (this)
            {
                if (_attributes.IsValueCreated)
                    _attributes.Value.Clear();
            }
        }

        /// <summary>
        /// Sets the current attributes to the passed GeometryAttributesDictionary
        /// </summary>
        /// <param name="attributes">GeometryAttributesDictionary to set these attribute values to</param>
        public void SetAttributes(GeometryAttributes attributes)
        {
            if (_asReadOnly)
                return;

            if (attributes == null)
                return;

            _attributes.Value.Clear();
            foreach (var key in attributes.Keys)
            {
                _attributes.Value.Add(key, attributes[key]);
            }
        }

        #endregion Methods
    }
}